"use strict";
/**
 * Resource Static Method:all, query, getById, getByIds, set
 * Resource Instance Mthod: $id, $save, $update, $updateC, $set, $remove, $saveOrUpdate
 */
angular.module('resource', []).factory('Resource', ["$http", "$q", function($http, $q) {
  function ResourceFactory(name) {
    var url = "res/" + name;
    var defaultParams = {}, _defaultParams = {headers:"application/json"};
    var Resource = function(data) {
      angular.extend(this, data);
    };
    var PromiseToQuery = function(){};

    var thenFactoryMethod = function(httpPromise, successcb, errorcb, isArray) {
      var scb = successcb || angular.noop;
      var ecb = errorcb || angular.noop;
      var i;

      return httpPromise.then(function(response) {
        var result;
        if (isArray) { // only Resource.query set isArray = true
          result = [];
          for (i = 0; i < response.data.length; i++) {
            result.push(new Resource(response.data[i]));
          }
        } else {
          //MongoLab has rather peculiar way of reporting not-found items, I would expect 404 HTTP response status...
          //if (response.data === " null ") {
          if (!response) {
            return $q.reject({
              code: 'resource.notfound',
              collection: name
            });
          } else {
            result = new Resource(response.data);
          }
        }
        scb(result, response.status, response.headers, response.config);
        return result;
      }, function(response) {
        ecb(undefined, response.status, response.headers, response.config);
        return undefined;
      });
    };

    /*
      Resource method: instance do not has the following method.
      and we won't mess the instance with the Resource itself up.
      */
    Resource.all = function(cb, errorcb) {
      return Resource.query({}, cb, errorcb);
    };
    /**
     * query for resources, the paramet will append with url
     * such as: http://localhost:8080/note/doo/res/entry?l=10&q=%7B%7D
     * ${url} => http://localhost:8080/note/doo/res/entry
     * ${params} => {l:10, q:{}}
     * @param  {[type]} queryJson   condition to query
     * @param  {[type]} successcb   function(result, response.status, response.headers, response.config)
     * @param  {[type]} errorcb     function(result, response.status, response.headers, response.config)
     * @param  {[type]} extraParams most of the time, we do not set this param and use the proxy class PromiseToQuery to handle it
     * @return {[type]}             [description]
     */
    Resource.query = function(queryJson, successcb, errorcb, extraParams) {
      for(var key in queryJson) {
    	  if (/^(limit|skip|count|field|single|sortby)$/.test(key)) throw Exception("queryJson cannot contain meta info");
      }
      var httpPromise = $http.get(url, {
        params: angular.extend({}, defaultParams, queryJson, extraParams)
      });
      return thenFactoryMethod(httpPromise, successcb, errorcb, true);
    };

    /**
     * query for resource with a specific id.
     * such as: http://localhost:8080/note/doo/res/entry/1065322
     */
    Resource.getById = function(id, successcb, errorcb) {
      var httpPromise = $http.get(url + '/' + id, {
        params: defaultParams
      });
      return thenFactoryMethod(httpPromise, successcb, errorcb);
    };

    /**
     * [Static Method]
     * Set the params for query, using the temp PromiseToQuery class;
     * usually resouce.setParam("limit", 2).setParam("field", {author: 1}).query({}, sucFn, errFn)
     * @param name
     * @param value
     * @returns {Object} the Resource itself for chaining
     */
    Resource.set = function(name, value) {
      var ptq = new PromiseToQuery();
      ptq.set(name, value);
      return ptq; //so that we can chain, resource.setParam("limit", 2).setParam("field", {author: 1}).query({})
    };

    function PromiseToQuery(){}
    PromiseToQuery.prototype.set = function(name, value) {
    	/*
      var map = {
            "limit": "l",
            "skip": "sk",
            "count": "c",
            "field": "f",
            "single": "fo",
            "sortby": "s"
          },
          key = map[name];
*/
    	var key = name;
      this.params = this.params || {};
      if (key) {
        this.params[key] = value;
      } else {
        throw new Error("No key found in PromiseToQuery");
      }
      return this; // so that we can chain
    };

    PromiseToQuery.prototype.query = function(queryJSON, sFn, eFn) {
      return Resource.query(queryJSON, sFn, eFn, this.params);
    };

    //instance methods
    Resource.prototype.$id = function() {
      if (this.id) {
        return this.id;
      }
    };

    /**
     * save use post to save data in case the data is too large for GET.
     * @param successcb
     * @param errorcb
     * @returns
     */
    Resource.prototype.$save = function(successcb, errorcb) {
      var httpPromise = $http.post(url, this, {
        params: defaultParams
      });
      return thenFactoryMethod(httpPromise, successcb, errorcb);
    };

    Resource.prototype.$update = function(successcb, errorcb) {
      var httpPromise = $http.put(url + "/" + this.$id(), angular.extend({}, this, {
        _id: undefined
      }), {
        params: defaultParams
      });
      return thenFactoryMethod(httpPromise, successcb, errorcb);
    };

	  /**
	   * http://docs.mongodb.org/manual/core/update/
	   * send Action as `inc`, `set` to update the Resource.
	   * @param action Sample: {"_name": "set", "body": { 'name.middle': 'Warner' }}, _name and body are required!!
	   * @param successcb
	   * @param errorcb
	   * @returns {*}
	   */
    Resource.prototype.$updateC = function(action, successcb, errorcb) {
      var httpPromise = $http.put(url + "/" + this.$id() + "/" + action._name, /* url */
      action.body || {}, /* request body */
	      {
        params: angular.extend({}, defaultParams)
      } /* empty by default*/ );
      var me = this;

      return thenFactoryMethod(httpPromise, function(res, status, headers, config) {
        angular.extend(me, res instanceof Resource ? res : {}); //update me
        successcb && successcb.apply(this, arguments);
      }, errorcb);
    };

	  /**
	   * send $set operation to server
	   * @param obj sould be {key1: value1, key2: value2}
	   * @returns {*}
     * @param successcb
		 * @param errorcb
	   */
	  Resource.prototype.$set = function(obj, successcb, errorcb) {
		  return this.$updateC({
			  "_name": "set",
			  "body": obj
		  }, successcb, errorcb);
	  }

    Resource.prototype.$remove = function(successcb, errorcb) {
      var httpPromise = $http['delete'](url + "/" + this.$id(), {
        params: defaultParams
      });
      return thenFactoryMethod(httpPromise, successcb, errorcb);
    };

    Resource.prototype.$saveOrUpdate = function(savecb, updatecb, errorSavecb, errorUpdatecb) {
      return this.$id() ? this.$update(updatecb, errorUpdatecb) : this.$save(savecb, errorSavecb);
    };

    return Resource;
  }

  return ResourceFactory;
}]);